﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Arduino.cs" company="Jarno Burger">
//   Jarno Burger
// </copyright>
// <summary>
//   Defines the Arduino type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace MyDomotica
{
    using System;
    using System.ComponentModel;

    using CommandMessenger;
    using CommandMessenger.Queue;
    using CommandMessenger.Transport.Serial;

    /// <summary>
    /// The class that com
    /// </summary>
    public class Arduino
    {
        #region Declare
        /// <summary>
        /// The class that configures and streams over the com port.
        /// </summary>
        private SerialTransport serialTransport;

        /// <summary>
        /// The class that does the communication with the arduino over given SerialTransport.
        /// On the arduino is also a installed CmdMessenger-lib.
        /// </summary>
        private CmdMessenger commandMessenger;

        /// <summary>
        /// This tells that this class has a debug event.
        /// </summary>
        public EventHandler<string> EventDebug;

        /// <summary>
        /// The last time we received data.
        /// </summary>
        private DateTime lastReceivedDataTime;

        /// <summary>
        /// The model that contains all current data.
        /// </summary>
        private readonly Model.Model model;
        #endregion

        #region Initialize
        /// <summary>
        /// Initializes a new instance of the <see cref="Arduino"/> class.
        /// </summary>
        /// <param name="model">The model.</param>
        public Arduino(Model.Model model)
        {
            this.model = model;
            this.model.PropertyChanged += this.OnPropertyChanged;
        }

        #endregion

        #region Main Actions
        /// <summary>
        /// Initializes the communication between the PC and the arduino.
        /// </summary>
        public void InitializeCommunication()
        {
            this.serialTransport = new SerialTransport
            {
                CurrentSerialSettings =
                {
                    PortName = "COM4",
                    BaudRate = 9600,
                    DtrEnable = true,
                }
            };

            this.commandMessenger = new CmdMessenger(this.serialTransport);
            this.commandMessenger.AddReceiveCommandStrategy(new StaleGeneralStrategy(1000));

            // attach callbacks
            this.commandMessenger.Attach(this.OnReceivedUnknownCommand);
            this.commandMessenger.Attach((int)ArduinoCommands.Error, this.OnError);
            this.commandMessenger.Attach((int)ArduinoCommands.InfoMessage, this.OnInfoMessage);
            this.commandMessenger.Attach((int)ArduinoCommands.StatusSensor, this.OnSensorMessage);
            this.commandMessenger.Attach((int)ArduinoCommands.StatusMotion, this.OnMotionMessage);
            this.commandMessenger.Attach((int)ArduinoCommands.Alive, this.OnKeepAlive);

            // attach logging stuff
            this.commandMessenger.NewLineSent += this.OnArduinoSentNewLine;
            this.commandMessenger.NewLineReceived += this.OnArduinoLineReceived;

            this.commandMessenger.Connect();

            this.Debug("Now listening to arduino.");
        }

        /// <summary>
        /// Breaks the communication with the arduino.
        /// </summary>
        public void BreakCommunication()
        {
            if (this.commandMessenger != null)
            {
                this.commandMessenger.Disconnect();
                this.commandMessenger.Dispose();
                this.commandMessenger = null;
            }

            if (this.serialTransport != null)
            {
                this.serialTransport.Dispose();
                this.serialTransport = null;
            }

            this.Debug("Communication classes to arduino are broken down.");

            System.Threading.Thread.Sleep(3000);
        }

        /// <summary>
        /// make sure that there is communication.
        /// </summary>
        /// <returns>true when communication is kept alive.</returns>
        public bool KeepCommunicationAlive()
        {
            var connected = this.serialTransport.IsConnected();

            // this.Debug("Communication is alive = " + connected);
            if (connected == false)
            {
                this.Debug("Serial transport disconnected.");
                this.BreakCommunication();
                this.InitializeCommunication();
                return this.serialTransport.IsConnected();
            }

            if (this.lastReceivedDataTime.AddSeconds(20) < DateTime.Now)
            {
                this.Debug("Last received > 20 sec.");
                this.BreakCommunication();
                this.InitializeCommunication();
                return this.serialTransport.IsConnected();
            }

            return this.serialTransport.IsConnected();
        }
        #endregion

        #region React to changes in model
        /// <summary>
        /// Called when a [property changed] in MODEL.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="propertyChangedEventArgs">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            if (propertyChangedEventArgs.PropertyName != "Heater")
            {
                return;
            }

            if (this.model.CurrentHeaterIsOn == true)
            {
                this.SendTurnOnHeaterCommand();
            }
            else
            {
                this.SendTurnOffHeaterCommand();
            }
        }
        #endregion

        #region Actions to send
        /// <summary>
        /// Sends the "turn on heater" command.
        /// </summary>
        public void SendTurnOnHeaterCommand()
        {
            this.Debug("Sending TurnOnHeater-command to arduino.");
            var command = new SendCommand((int)ArduinoCommands.CmdTurnOnHeating);
            this.commandMessenger.SendCommand(command);
        }

        /// <summary>
        /// Sends the "turn off heater" command.
        /// </summary>
        public void SendTurnOffHeaterCommand()
        {
            this.Debug("Sending TurnOffHeater-command to arduino.");
            var command = new SendCommand((int)ArduinoCommands.CmdTurnOffHeating);
            this.commandMessenger.SendCommand(command);
        }
        #endregion

        #region Handle Arduino messages

        /// <summary>
        /// Called when we receive a keep alive signal [keep alive].
        /// </summary>
        /// <param name="receivedCommand">The received command.</param>
        private void OnKeepAlive(ReceivedCommand receivedCommand)
        {
            // this.Debug("Keep alive " + receivedCommand.Arguments[0]);
        }

        /// <summary>
        /// I received a unknown command.
        /// </summary>
        /// <param name="receivedcommand">The received command.</param>
        private void OnReceivedUnknownCommand(ReceivedCommand receivedcommand)
        {
            this.Debug(@"Received unknown command.");
        }

        /// <summary>
        /// I received a error.
        /// </summary>
        /// <param name="receivedcommand">The receivedCommand.</param>
        private void OnError(ReceivedCommand receivedcommand)
        {
            this.Debug(@"Arduino has experienced an error : " + receivedcommand.RawString);
        }

        /// <summary>
        /// Called when i received a [information message].
        /// </summary>
        /// <param name="receivedcommand">The received command.</param>
        private void OnInfoMessage(ReceivedCommand receivedcommand)
        {
            this.Debug("Info ! : " + receivedcommand.RawString);
        }

        /// <summary>
        /// Called when i received a [sensor message].
        /// </summary>
        /// <param name="receivedcommand">The received command.</param>
        private void OnSensorMessage(ReceivedCommand receivedcommand)
        {
            // get current humidity
            var humidity = Math.Round(receivedcommand.ReadFloatArg(), 1);
            this.model.CurrentHumidity = (float)humidity;

            // get current temperature
            var temperature = Math.Round(receivedcommand.ReadFloatArg(), 1);
            this.model.CurrentTemperature = (float)temperature;

            // get current brightness
            var brightness = Math.Round(receivedcommand.ReadFloatArg());
            this.model.CurrentBrightness = (float)brightness;

            this.lastReceivedDataTime = DateTime.Now;

            this.model.CurrentMessage =
                                        "Temp " + this.model.CurrentTemperature +
                                        " Humid " + this.model.CurrentHumidity +
                                        " Bright " + (int)this.model.CurrentBrightness;

            // this.Debug("Read sensors : " + this.model.CurrentMessage);
        }

        /// <summary>
        /// Called when i received a [motion message].
        /// </summary>
        /// <param name="receivedcommand">The received command.</param>
        private void OnMotionMessage(ReceivedCommand receivedcommand)
        {
            this.model.CurrentMotion = receivedcommand.ReadBoolArg();
            if (this.model.CurrentMotion)
            {
                this.model.CurrentMessage = "Motion detected";
            }
            else
            {
                this.model.CurrentMessage = "Motion ended";
            }

            this.lastReceivedDataTime = DateTime.Now;
        }

        /// <summary>
        /// Called when the [arduino sent new line].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="commandEventArgs">The <see cref="CommandEventArgs"/> instance containing the event data.</param>
        private void OnArduinoSentNewLine(object sender, CommandEventArgs commandEventArgs)
        {
            this.Debug(@"Arduino sending : " + commandEventArgs.Command.CommandString());
        }

        /// <summary>
        /// Called when [arduino line received].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="commandEventArgs">The <see cref="CommandEventArgs"/> instance containing the event data.</param>
        private void OnArduinoLineReceived(object sender, CommandEventArgs commandEventArgs)
        {
            // this.Debug(@"Arduino received : " + this.commandMessenger.CurrentReceivedLine);
        }
        #endregion

        #region Debug
        /// <summary>
        /// Creates a debug event out of the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void Debug(string message)
        {
            if (this.EventDebug != null)
            {
                this.EventDebug(this, message);
            }
            else
            {
                System.Diagnostics.Debug.WriteLine(message);
            }
        }
        #endregion
    }
}
